/*
 * Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "DatabaseEnv.h"
#include "Mail.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "Opcodes.h"
#include "Log.h"
#include "World.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "Language.h"
#include "DBCStores.h"
#include "Item.h"
#include "AccountMgr.h"

void WorldSession::HandleSendMail(WorldPacket& recvData)
{
    ObjectGuid mailbox;
    uint64 money, COD;
    std::string receiver, subject, body;
    uint32 bodyLength, subjectLength, receiverLength;
	uint32 unk1, unk2;
	uint8 itemCount;

	recvData >> unk1 >> unk2;                       // both unknown
	recvData >> COD >> money;                       // money and cod
        
	mailbox[0] = recvData.ReadBit();
	mailbox[6] = recvData.ReadBit();
	mailbox[4] = recvData.ReadBit();
	mailbox[1] = recvData.ReadBit();
	bodyLength = recvData.ReadBits(11);
	mailbox[3] = recvData.ReadBit();
	receiverLength = recvData.ReadBits(9);
	mailbox[7] = recvData.ReadBit();
	mailbox[5] = recvData.ReadBit();
	itemCount = recvData.ReadBits(5);               // attached items count

	if (itemCount > MAX_MAIL_ITEMS)                       // client limit
    {
        GetPlayer()->SendMailResult(0, MAIL_SEND, MAIL_ERR_TOO_MANY_ATTACHMENTS);
        recvData.rfinish();                   // set to end to avoid warnings spam
        return;
    }

	ObjectGuid itemGuids[MAX_MAIL_ITEMS];
    uint8 bitOrder[8] = {1, 7, 2, 5, 0, 6, 3, 4};
    for (uint8 i = 0; i < itemCount; ++i)
		recvData.ReadBitInOrder(itemGuids[i], bitOrder);

	subjectLength = recvData.ReadBits(9);
	mailbox[2] = recvData.ReadBit();

    for (uint8 i = 0; i < itemCount; ++i)
	{
		recvData.read_skip<uint8>();                // item slot in mail, not used
		recvData.ReadByteSeq(itemGuids[i][3]);
		recvData.ReadByteSeq(itemGuids[i][0]);
		recvData.ReadByteSeq(itemGuids[i][2]);
		recvData.ReadByteSeq(itemGuids[i][1]);
		recvData.ReadByteSeq(itemGuids[i][6]);
		recvData.ReadByteSeq(itemGuids[i][5]);
		recvData.ReadByteSeq(itemGuids[i][7]);
		recvData.ReadByteSeq(itemGuids[i][4]);
    }

	recvData.ReadByteSeq(mailbox[1]);
	body = recvData.ReadString(bodyLength);
	recvData.ReadByteSeq(mailbox[0]);
	subject = recvData.ReadString(subjectLength);
	recvData.ReadByteSeq(mailbox[2]);
	recvData.ReadByteSeq(mailbox[6]);
	recvData.ReadByteSeq(mailbox[5]);
	recvData.ReadByteSeq(mailbox[7]);
	recvData.ReadByteSeq(mailbox[3]);
	recvData.ReadByteSeq(mailbox[4]);
	receiver = recvData.ReadString(receiverLength);

    // packet read complete, now do check

    if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
        return;

    if (receiver.empty())
        return;

    Player* player = _player;

    if (player->getLevel() < sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ))
    {
        SendNotification(GetTrinityString(LANG_MAIL_SENDER_REQ), sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ));
        return;
    }

    uint64 rc = 0;
    if (normalizePlayerName(receiver))
        rc = sObjectMgr->GetPlayerGUIDByName(receiver);

    if (!rc)
    {
        sLog->outInfo(LOG_FILTER_NETWORKIO, "Player %u is sending mail to %s (GUID: not existed!) with subject %s and body %s includes %u items, " UI64FMTD " copper and " UI64FMTD " COD copper with unk1 = %u, unk2 = %u",
            player->GetGUIDLow(), receiver.c_str(), subject.c_str(), body.c_str(), itemCount, money, COD, unk1, unk2);
        player->SendMailResult(0, MAIL_SEND, MAIL_ERR_RECIPIENT_NOT_FOUND);
        return;
    }

    sLog->outInfo(LOG_FILTER_NETWORKIO, "Player %u is sending mail to %s (GUID: %u) with subject %s and body %s includes %u items, " UI64FMTD " copper and " UI64FMTD " COD copper with unk1 = %u, unk2 = %u", player->GetGUIDLow(), receiver.c_str(), GUID_LOPART(rc), subject.c_str(), body.c_str(), itemCount, money, COD, unk1, unk2);

    if (player->GetGUID() == rc)
    {
        player->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANNOT_SEND_TO_SELF);
        return;
    }

	uint32 cost = itemCount ? 30 * itemCount : 30;  // price hardcoded in client

    uint64 reqmoney = cost + money;

    if (!player->HasEnoughMoney(reqmoney) && !player->isGameMaster())
    {
        player->SendMailResult(0, MAIL_SEND, MAIL_ERR_NOT_ENOUGH_MONEY);
        return;
    }

    Player* receive = ObjectAccessor::FindPlayer(rc);

    uint32 rc_team = 0;
    uint8 mails_count = 0;                                  //do not allow to send to one player more than 100 mails

    if (receive)
    {
        rc_team = receive->GetTeam();
        mails_count = receive->GetMailSize();
    }

    //do not allow to have more than 100 mails in mailbox.. mails count is in opcode uint8!!! - so max can be 255..
    if (mails_count > 100)
    {
        player->SendMailResult(0, MAIL_SEND, MAIL_ERR_RECIPIENT_CAP_REACHED);
        return;
    }
    // test the receiver's Faction... or all items are account bound
	bool accountBound = itemCount ? true : false;
	for (uint8 i = 0; i < itemCount; ++i)
    {
		Item* item = player->GetItemByGuid(itemGuids[i]);
        if (item)
        {
            ItemTemplate const* itemProto = item->GetTemplate();
            if (!itemProto || !(itemProto->Flags & ITEM_PROTO_FLAG_BIND_TO_ACCOUNT))
            {
                accountBound = false;
                break;
            }
        }
    }

	if (!accountBound && !sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_MAIL) && player->GetTeam() != rc_team && AccountMgr::IsPlayerAccount(GetSecurity()))
    {
        player->SendMailResult(0, MAIL_SEND, MAIL_ERR_NOT_YOUR_TEAM);
        return;
    }

    uint32 rc_account = receive
        ? receive->GetSession()->GetAccountId()
        : sObjectMgr->GetPlayerAccountIdByGUID(rc);

    Item* items[MAX_MAIL_ITEMS];

	for (uint8 i = 0; i < itemCount; ++i)
    {
		if (!itemGuids[i])
        {
            player->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
            return;
        }

		Item* item = player->GetItemByGuid(itemGuids[i]);

        // prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail)
        if (!item)
        {
            player->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
            return;
        }

        if (!item->CanBeTraded(true))
        {
            player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM);
            return;
        }

        if (item->IsBoundAccountWide() && item->IsSoulBound() && player->GetSession()->GetAccountId() != rc_account)
        {
            player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_NOT_SAME_ACCOUNT);
            return;
        }

        if (item->GetTemplate()->Flags & ITEM_PROTO_FLAG_CONJURED || item->GetUInt32Value(ITEM_FIELD_DURATION))
        {
            player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM);
            return;
        }

        if (COD && item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_WRAPPED))
        {
            player->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANT_SEND_WRAPPED_COD);
            return;
        }

        if (item->IsNotEmptyBag())
        {
            player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_DESTROY_NONEMPTY_BAG);
            return;
        }

        items[i] = item;
    }

    // Check for spamming
    if (!UpdateAntispamCount())
    {
        player->SendMailResult(0, MAIL_SEND, MAIL_ERR_INTERNAL_ERROR);
        SendNotification(GetTrinityString(LANG_ANTISPAM_ERROR));
        return;
    }

    // Check for special symbols
    if (!checkMailText(subject) ||  !checkMailText(body))
    {
        player->SendMailResult(0, MAIL_SEND, MAIL_ERR_INTERNAL_ERROR);
        return;
    }

    player->SendMailResult(0, MAIL_SEND, MAIL_OK);

    player->ModifyMoney(-int64(reqmoney));
    player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_MAIL, cost);

    bool needItemDelay = false;

    MailDraft draft(subject, body);

    SQLTransaction trans = CharacterDatabase.BeginTransaction();

	if (itemCount > 0 || money > 0)
    {
		if (itemCount > 0)
        {
			for (uint8 i = 0; i < itemCount; ++i)
            {
                Item* item = items[i];
                if (!AccountMgr::IsPlayerAccount(GetSecurity()) && sWorld->getBoolConfig(CONFIG_GM_LOG_TRADE))
                {
                    sLog->outCommand(GetAccountId(), "", GetPlayer()->GetGUIDLow(), GetPlayer()->GetName(),
                                    rc_account, "", 0, receiver.c_str(),
                                    "GM %s (Account: %u) mail item: %s (Entry: %u Count: %u) to player: %s (Account: %u)",
                                    GetPlayerName().c_str(), GetAccountId(), item->GetTemplate()->Name1.c_str(), item->GetEntry(), item->GetCount(), receiver.c_str(), rc_account);
                }

                item->SetNotRefundable(GetPlayer()); // makes the item no longer refundable
                player->MoveItemFromInventory(items[i]->GetBagSlot(), item->GetSlot(), true);

                item->DeleteFromInventoryDB(trans);     // deletes item from character's inventory
                item->SetOwnerGUID(rc);
                item->SaveToDB(trans);                  // recursive and not have transaction guard into self, item not in inventory and can be save standalone

                draft.AddItem(item);
            }

            // if item send to character at another account, then apply item delivery delay
            needItemDelay = player->GetSession()->GetAccountId() != rc_account;
        }

        if (money > 0 && !AccountMgr::IsPlayerAccount(GetSecurity()) && sWorld->getBoolConfig(CONFIG_GM_LOG_TRADE))
        {
            //TODO: character guid
            sLog->outCommand(GetAccountId(), "", GetPlayer()->GetGUIDLow(), GetPlayer()->GetName(),
                            rc_account, "", 0, receiver.c_str(),
                            "GM %s (Account: %u) mail money: %u to player: %s (Account: %u)",
                            GetPlayerName().c_str(), GetAccountId(), money, receiver.c_str(), rc_account);
        }
    }

    // Guild Mail
    if (receive && receive->GetGuildId() && player->GetGuildId())
        if (player->HasAura(83951) && (player->GetGuildId() == receive->GetGuildId()))
            needItemDelay = false;

    // If theres is an item, there is a one hour delivery delay if sent to another account's character.
    uint32 deliver_delay = needItemDelay ? sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY) : 0;

    // will delete item or place to receiver mail list
    draft
        .AddMoney(money)
        .AddCOD(COD)
        .SendMailTo(trans, MailReceiver(receive, GUID_LOPART(rc)), MailSender(player), body.empty() ? MAIL_CHECK_MASK_COPIED : MAIL_CHECK_MASK_HAS_BODY, deliver_delay);

    player->SaveInventoryAndGoldToDB(trans);
    CharacterDatabase.CommitTransaction(trans);
}

//called when mail is read
void WorldSession::HandleMailMarkAsRead(WorldPacket & recvData)
{
    ObjectGuid mailbox;
    uint32 mailId;

    recvData >> mailId;

	mailbox[0] = recvData.ReadBit();
	mailbox[2] = recvData.ReadBit();
	mailbox[3] = recvData.ReadBit();
	recvData.ReadBit();
	mailbox[4] = recvData.ReadBit();
	mailbox[6] = recvData.ReadBit();
	mailbox[7] = recvData.ReadBit();
	mailbox[1] = recvData.ReadBit();
	mailbox[5] = recvData.ReadBit();

	recvData.FlushBits();

	recvData.ReadByteSeq(mailbox[1]);
	recvData.ReadByteSeq(mailbox[7]);
	recvData.ReadByteSeq(mailbox[2]);
	recvData.ReadByteSeq(mailbox[5]);
	recvData.ReadByteSeq(mailbox[6]);
	recvData.ReadByteSeq(mailbox[3]);
	recvData.ReadByteSeq(mailbox[4]);
	recvData.ReadByteSeq(mailbox[0]);

    if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
        return;

    Player* player = _player;
    Mail* m = player->GetMail(mailId);
    if (m)
    {
        if (player->unReadMails)
            --player->unReadMails;
        m->checked = m->checked | MAIL_CHECK_MASK_READ;
        player->m_mailsUpdated = true;
        m->state = MAIL_STATE_CHANGED;
    }
}

//called when client deletes mail
void WorldSession::HandleMailDelete(WorldPacket & recvData)
{
    uint32 unk, mailId;
	recvData >> mailId >> unk;

    //if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
    //    return;

    Mail* m = _player->GetMail(mailId);
    Player* player = _player;
    player->m_mailsUpdated = true;
    if (m)
    {
        // delete shouldn't show up for COD mails
        if (m->COD)
        {
            player->SendMailResult(mailId, MAIL_DELETED, MAIL_ERR_INTERNAL_ERROR);
            return;
        }

        m->state = MAIL_STATE_DELETED;
    }
    player->SendMailResult(mailId, MAIL_DELETED, MAIL_OK);
}

void WorldSession::HandleMailReturnToSender(WorldPacket & recvData)
{
    ObjectGuid mailbox;
    uint32 mailId;

    recvData >> mailId;

	mailbox[2] = recvData.ReadBit();
	mailbox[0] = recvData.ReadBit();
	mailbox[4] = recvData.ReadBit();
	mailbox[6] = recvData.ReadBit();
	mailbox[3] = recvData.ReadBit();
	mailbox[1] = recvData.ReadBit();
	mailbox[7] = recvData.ReadBit();
	mailbox[5] = recvData.ReadBit();

	recvData.ReadByteSeq(mailbox[5]);
	recvData.ReadByteSeq(mailbox[6]);
	recvData.ReadByteSeq(mailbox[2]);
	recvData.ReadByteSeq(mailbox[0]);
	recvData.ReadByteSeq(mailbox[3]);
	recvData.ReadByteSeq(mailbox[1]);
	recvData.ReadByteSeq(mailbox[4]);
	recvData.ReadByteSeq(mailbox[7]);

    if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
        return;

    Player* player = _player;
    Mail* m = player->GetMail(mailId);
    if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
    {
        player->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER, MAIL_ERR_INTERNAL_ERROR);
        return;
    }
    //we can return mail now
    //so firstly delete the old one
    SQLTransaction trans = CharacterDatabase.BeginTransaction();

    PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
    stmt->setUInt32(0, mailId);
    trans->Append(stmt);

    stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
    stmt->setUInt32(0, mailId);
    trans->Append(stmt);

    player->RemoveMail(mailId);

    // only return mail if the player exists (and delete if not existing)
    if (m->messageType == MAIL_NORMAL && m->sender)
    {
        MailDraft draft(m->subject, m->body);
        if (m->mailTemplateId)
            draft = MailDraft(m->mailTemplateId, false);     // items already included

        if (m->HasItems())
        {
            for (MailItemInfoVec::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2)
            {
                Item* item = player->GetMItem(itr2->item_guid);
                if (item)
                    draft.AddItem(item);
                else
                {
                    //WTF?
                }

                player->RemoveMItem(itr2->item_guid);
            }
        }
        draft.AddMoney(m->money).SendReturnToSender(GetAccountId(), m->receiver, m->sender, trans);
    }

    CharacterDatabase.CommitTransaction(trans);

    delete m;                                               //we can deallocate old mail
    player->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER, MAIL_OK);
}

//called when player takes item attached in mail
void WorldSession::HandleMailTakeItem(WorldPacket& recvData)
{
    ObjectGuid mailbox;
    uint32 mailId;
    uint32 itemId;

	recvData >> mailId;
	recvData >> itemId;

	mailbox[6] = recvData.ReadBit();
	mailbox[5] = recvData.ReadBit();
	mailbox[2] = recvData.ReadBit();
	mailbox[3] = recvData.ReadBit();
	mailbox[0] = recvData.ReadBit();
	mailbox[1] = recvData.ReadBit();
	mailbox[4] = recvData.ReadBit();
	mailbox[7] = recvData.ReadBit();

	recvData.ReadByteSeq(mailbox[0]);
	recvData.ReadByteSeq(mailbox[1]);
	recvData.ReadByteSeq(mailbox[4]);
	recvData.ReadByteSeq(mailbox[2]);
	recvData.ReadByteSeq(mailbox[5]);
	recvData.ReadByteSeq(mailbox[6]);
	recvData.ReadByteSeq(mailbox[3]);
	recvData.ReadByteSeq(mailbox[7]);

    if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
        return;

    Player* player = _player;

    Mail* m = player->GetMail(mailId);
    if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
    {
        player->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_INTERNAL_ERROR);
        return;
    }

    // prevent cheating with skip client money check
    if (!player->HasEnoughMoney(uint64(m->COD)))
    {
        player->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_NOT_ENOUGH_MONEY);
        return;
    }

    Item* item = player->GetMItem(itemId);

    ItemPosCountVec dest;
    uint8 msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, item, false);
    if (msg == EQUIP_ERR_OK)
    {
        SQLTransaction trans = CharacterDatabase.BeginTransaction();
        m->RemoveItem(itemId);
        m->removedItems.push_back(itemId);

        if (m->COD > 0)                                     //if there is COD, take COD money from player and send them to sender by mail
        {
            uint64 sender_guid = MAKE_NEW_GUID(m->sender, 0, HIGHGUID_PLAYER);
            Player* receive = ObjectAccessor::FindPlayer(sender_guid);

            uint32 sender_accId = 0;

            if (!AccountMgr::IsPlayerAccount(GetSecurity()) && sWorld->getBoolConfig(CONFIG_GM_LOG_TRADE))
            {
                std::string sender_name;
                if (receive)
                {
                    sender_accId = receive->GetSession()->GetAccountId();
                    sender_name = receive->GetName();
                }
                else
                {
                    // can be calculated early
                    sender_accId = sObjectMgr->GetPlayerAccountIdByGUID(sender_guid);

                    if (!sObjectMgr->GetPlayerNameByGUID(sender_guid, sender_name))
                        sender_name = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
                }
                sLog->outCommand(GetAccountId(), "", GetPlayer()->GetGUIDLow(), GetPlayer()->GetName(),
                                sender_accId, "", sender_guid, sender_name.c_str(),
                                "GM %s (Account: %u) receive mail item: %s (Entry: %u Count: %u) and send COD money: %u to player: %s (Account: %u)",
                                GetPlayerName().c_str(), GetAccountId(), item->GetTemplate()->Name1.c_str(), item->GetEntry(), item->GetCount(), m->COD, sender_name.c_str(), sender_accId);
            }
            else if (!receive)
                sender_accId = sObjectMgr->GetPlayerAccountIdByGUID(sender_guid);

            // check player existence
            if (receive || sender_accId)
            {
                MailDraft(m->subject, "")
                    .AddMoney(m->COD)
                    .SendMailTo(trans, MailReceiver(receive, m->sender), MailSender(MAIL_NORMAL, m->receiver), MAIL_CHECK_MASK_COD_PAYMENT);
            }

            player->ModifyMoney(-int32(m->COD));
        }
        m->COD = 0;
        m->state = MAIL_STATE_CHANGED;
        player->m_mailsUpdated = true;
        player->RemoveMItem(item->GetGUIDLow());

        uint32 count = item->GetCount();                      // save counts before store and possible merge with deleting
        item->SetState(ITEM_UNCHANGED);                       // need to set this state, otherwise item cannot be removed later, if neccessary
        player->MoveItemToInventory(dest, item, true);

        player->SaveInventoryAndGoldToDB(trans);
        player->_SaveMail(trans);
        CharacterDatabase.CommitTransaction(trans);

        player->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_OK, 0, itemId, count);
    }
    else
        player->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_EQUIP_ERROR, msg);
}

void WorldSession::HandleMailTakeMoney(WorldPacket& recvData)
{
    ObjectGuid mailbox;
    uint64 money;
    uint32 mailId;

	recvData >> mailId;
	recvData >> money;

	mailbox[7] = recvData.ReadBit();
	mailbox[6] = recvData.ReadBit();
	mailbox[3] = recvData.ReadBit();
	mailbox[2] = recvData.ReadBit();
	mailbox[4] = recvData.ReadBit();
	mailbox[5] = recvData.ReadBit();
	mailbox[0] = recvData.ReadBit();
	mailbox[1] = recvData.ReadBit();

	recvData.ReadByteSeq(mailbox[7]);
	recvData.ReadByteSeq(mailbox[1]);
	recvData.ReadByteSeq(mailbox[4]);
	recvData.ReadByteSeq(mailbox[0]);
	recvData.ReadByteSeq(mailbox[3]);
	recvData.ReadByteSeq(mailbox[2]);
	recvData.ReadByteSeq(mailbox[6]);
	recvData.ReadByteSeq(mailbox[5]);

    if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
        return;

    Player* player = _player;

    Mail* m = player->GetMail(mailId);
    if ((!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) ||
        (money > 0 && m->money != money))
    {
        player->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_ERR_INTERNAL_ERROR);
        return;
    }

    player->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_OK);

    player->ModifyMoney(money);
    m->money = 0;
    m->state = MAIL_STATE_CHANGED;
    player->m_mailsUpdated = true;

    // save money and mail to prevent cheating
    SQLTransaction trans = CharacterDatabase.BeginTransaction();
    player->SaveGoldToDB(trans);
    player->_SaveMail(trans);
    CharacterDatabase.CommitTransaction(trans);
}

//called when player lists his received mails
void WorldSession::HandleGetMailList(WorldPacket& recvData)
{
    ObjectGuid mailbox;

	mailbox[6] = recvData.ReadBit();
	mailbox[3] = recvData.ReadBit();
	mailbox[7] = recvData.ReadBit();
	mailbox[5] = recvData.ReadBit();
	mailbox[4] = recvData.ReadBit();
	mailbox[1] = recvData.ReadBit();
	mailbox[2] = recvData.ReadBit();
	mailbox[0] = recvData.ReadBit();

	recvData.ReadByteSeq(mailbox[7]);
	recvData.ReadByteSeq(mailbox[1]);
	recvData.ReadByteSeq(mailbox[6]);
	recvData.ReadByteSeq(mailbox[5]);
	recvData.ReadByteSeq(mailbox[4]);
	recvData.ReadByteSeq(mailbox[2]);
	recvData.ReadByteSeq(mailbox[3]);
	recvData.ReadByteSeq(mailbox[0]);

    if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
        return;

    Player* player = _player;

    //load players mails, and mailed items
    if (!player->m_mailsLoaded)
        player->_LoadMail();

    // client can't work with packets > max int16 value
    const uint32 maxPacketSize = 32767;

    uint32 mailsCount = 0;                                 // real send to client mails amount
    uint32 realCount  = 0;                                 // real mails amount
	time_t cur_time = time(NULL);
	ByteBuffer mailData;

    WorldPacket data(SMSG_MAIL_LIST_RESULT, 200);         // guess size
    data << uint32(0);                                      // real mail's count
	size_t mailCountPos = data.bitwpos();
	data.WriteBits(0, 18);                              // placeholder

    for (PlayerMails::iterator itr = player->GetMailBegin(); itr != player->GetMailEnd(); ++itr)
	{
		Mail* mail = *itr;

		// Only first 50 mails are displayed
		if (mailsCount >= 50)
		{
			realCount += 1;
			continue;
		}

		// skip deleted or not delivered (deliver delay not expired) mails
		if (mail->state == MAIL_STATE_DELETED || cur_time < mail->deliver_time)
			continue;

		// skip mail with more than MAX_MAIL_ITEMS items (should not occur)
		uint8 itemCount = mail->items.size();
		if (itemCount > MAX_MAIL_ITEMS)
		{
			realCount += 1;
			continue;
		}

		// skip mail if the packet has become too large (should not occur)
		size_t nextMailSize = 6 + 1 + 8 + itemCount * (4 + 4 + 4 + 4 + 4 + MAX_INSPECTED_ENCHANTMENT_SLOT * (4 + 4 + 4) +
			4 + 4 + 4 + 4 + 1 + 4) + mail->body.size() + mail->subject.size() + 4 + 4 + 8 + 4 + 8 + 4 + 4 + 1 + 4;

		if (data.wpos() + nextMailSize > maxPacketSize)
		{
			realCount += 1;
			continue;
		}

		data.WriteBit(mail->messageType != MAIL_NORMAL ? 1 : 0);
		data.WriteBits(mail->subject.size(), 8);
		data.WriteBits(mail->body.size(), 13);
		data.WriteBit(0);
		data.WriteBit(0);

		size_t itemCountPos = data.bitwpos();
		data.WriteBits(0, 17);                          // placeholder

		data.WriteBit(1);                               // has guid

		ObjectGuid guid = mail->messageType == MAIL_NORMAL ? MAKE_NEW_GUID(mail->sender, 0, HIGHGUID_PLAYER) : 0;
		data.WriteBit(guid[2]);
		data.WriteBit(guid[6]);
		data.WriteBit(guid[7]);
		data.WriteBit(guid[0]);
		data.WriteBit(guid[5]);
		data.WriteBit(guid[3]);
		data.WriteBit(guid[1]);
		data.WriteBit(guid[4]);

		uint8 trueItemCount = 0;
		for (uint8 i = 0; i < itemCount; i++)
		{
			Item* item = player->GetMItem(mail->items[i].item_guid);
			if (!item)
				continue;

			data.WriteBit(0);

			mailData << uint32(item->GetGUIDLow());
			mailData << uint32(4);                      // unknown
			mailData << uint32(item->GetSpellCharges());
			mailData << uint32(item->GetUInt32Value(ITEM_FIELD_DURABILITY));
			mailData << uint32(0);                      // unknown

			for (uint8 j = 0; j < MAX_INSPECTED_ENCHANTMENT_SLOT; j++)
			{
				mailData << uint32(item->GetEnchantmentCharges((EnchantmentSlot)j));
				mailData << uint32(item->GetEnchantmentDuration((EnchantmentSlot)j));
				mailData << uint32(item->GetEnchantmentId((EnchantmentSlot)j));
			}

			mailData << uint32(item->GetItemSuffixFactor());
			mailData << int32(item->GetItemRandomPropertyId());
			mailData << uint32(item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY));
			mailData << uint32(item->GetCount());
			mailData << uint8(i);
			mailData << uint32(item->GetEntry());

			trueItemCount++;
		}

		data.PutBits(itemCountPos, trueItemCount, 17);

		mailData.WriteString(mail->body);
		mailData << uint32(mail->messageID);
		mailData.WriteByteSeq(guid[4]);
		mailData.WriteByteSeq(guid[0]);
		mailData.WriteByteSeq(guid[5]);
		mailData.WriteByteSeq(guid[3]);
		mailData.WriteByteSeq(guid[1]);
		mailData.WriteByteSeq(guid[7]);
		mailData.WriteByteSeq(guid[2]);
		mailData.WriteByteSeq(guid[6]);
		mailData << uint32(mail->mailTemplateId);
		mailData << uint64(mail->COD);
		mailData.WriteString(mail->subject);
		mailData << uint32(mail->stationery);
		mailData << float(float(mail->expire_time - time(NULL)) / DAY);
		mailData << uint64(mail->money);
		mailData << uint32(mail->checked);

		if (mail->messageType != MAIL_NORMAL)
			mailData << uint32(mail->sender);

		mailData << uint8(mail->messageType);
		mailData << uint32(0);                          // unknown

		realCount++;
		mailsCount++;
    }

	data.FlushBits();
	data.append(mailData);

	data.put<uint32>(0, realCount);
	data.PutBits(mailCountPos, mailsCount, 18);

	SendPacket(&data);

    // recalculate m_nextMailDelivereTime and unReadMails
    _player->UpdateNextMailTimeAndUnreads();
}

//used when player copies mail body to his inventory
void WorldSession::HandleMailCreateTextItem(WorldPacket& recvData)
{
    ObjectGuid mailbox;
    uint32 mailId;

	recvData >> mailId;

	mailbox[4] = recvData.ReadBit();
	mailbox[1] = recvData.ReadBit();
	mailbox[6] = recvData.ReadBit();
	mailbox[2] = recvData.ReadBit();
	mailbox[5] = recvData.ReadBit();
	mailbox[3] = recvData.ReadBit();
	mailbox[0] = recvData.ReadBit();
	mailbox[7] = recvData.ReadBit();

	recvData.ReadByteSeq(mailbox[6]);
	recvData.ReadByteSeq(mailbox[5]);
	recvData.ReadByteSeq(mailbox[4]);
	recvData.ReadByteSeq(mailbox[3]);
	recvData.ReadByteSeq(mailbox[0]);
	recvData.ReadByteSeq(mailbox[7]);
	recvData.ReadByteSeq(mailbox[2]);
	recvData.ReadByteSeq(mailbox[1]);

    if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
        return;

    Player* player = _player;

    Mail* m = player->GetMail(mailId);
    if (!m || (m->body.empty() && !m->mailTemplateId) || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
    {
        player->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_INTERNAL_ERROR);
        return;
    }

    Item* bodyItem = new Item;                              // This is not bag and then can be used new Item.
    if (!bodyItem->Create(sObjectMgr->GenerateLowGuid(HIGHGUID_ITEM), MAIL_BODY_ITEM_TEMPLATE, player))
    {
        delete bodyItem;
        return;
    }

    // in mail template case we need create new item text
    if (m->mailTemplateId)
    {
        MailTemplateEntry const* mailTemplateEntry = sMailTemplateStore.LookupEntry(m->mailTemplateId);
        if (!mailTemplateEntry)
        {
            player->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_INTERNAL_ERROR);
            return;
        }

        bodyItem->SetText(mailTemplateEntry->content);
    }
    else
        bodyItem->SetText(m->body);

    bodyItem->SetUInt32Value(ITEM_FIELD_CREATOR, m->sender);
    bodyItem->SetFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_MAIL_TEXT_MASK);

    sLog->outInfo(LOG_FILTER_NETWORKIO, "HandleMailCreateTextItem mailid=%u", mailId);

    ItemPosCountVec dest;
    uint8 msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, bodyItem, false);
    if (msg == EQUIP_ERR_OK)
    {
        m->checked = m->checked | MAIL_CHECK_MASK_COPIED;
        m->state = MAIL_STATE_CHANGED;
        player->m_mailsUpdated = true;

        player->StoreItem(dest, bodyItem, true);
        player->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_OK);
    }
    else
    {
        player->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_EQUIP_ERROR, msg);
        delete bodyItem;
    }
}

//TODO Fix me! ... this void has probably bad condition, but good data are sent
void WorldSession::HandleQueryNextMailTime(WorldPacket & /*recvData*/)
{
    WorldPacket data(MSG_QUERY_NEXT_MAIL_TIME, 8);

    if (!_player->m_mailsLoaded)
        _player->_LoadMail();

    if (_player->unReadMails > 0)
    {
        data << float(0);                                  // float
        data << uint32(0);                                 // count

        uint32 count = 0;
        time_t now = time(NULL);
        std::set<uint32> sentSenders;
        for (PlayerMails::iterator itr = _player->GetMailBegin(); itr != _player->GetMailEnd(); ++itr)
        {
            Mail* m = (*itr);
            // must be not checked yet
            if (m->checked & MAIL_CHECK_MASK_READ)
                continue;

            // and already delivered
            if (now < m->deliver_time)
                continue;

            // only send each mail sender once
            if (sentSenders.count(m->sender))
                continue;

            data << uint64(m->messageType == MAIL_NORMAL ? m->sender : 0);  // player guid
            data << uint32(m->messageType != MAIL_NORMAL ? m->sender : 0);  // non-player entries
            data << uint32(m->messageType);
            data << uint32(m->stationery);
            data << float(m->deliver_time - now);

            sentSenders.insert(m->sender);
            ++count;
            if (count == 2)                                  // do not display more than 2 mails
                break;
        }

        data.put<uint32>(4, count);
    }
    else
    {
        data << float(-DAY);
        data << uint32(0);
    }

    SendPacket(&data);
}
